/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import com.bourre.commands.Command;
import com.bourre.commands.Delegate;
import com.bourre.core.HashCodeFactory;
import com.bourre.data.collections.Map;
import com.bourre.data.iterator.Iterator;

import fever.app.accelerator.KeyboardController;
import fever.app.accelerator.KeyDefinition;
import fever.events.KeyboardEvent;
import fever.log.FeverDebug;
import fever.utils.Stringifier;

/**
 * Defines shortcut mapping.
 * 
 * <p>{@link ShortcutMap} allows shortcuts switching during application life.<br />
 * You can {@link #load()} or {@link #unload()} shortcut map when you want to activate 
 * or not keys accelerator.
 * 
 * <p>Use {@link fever.app.accelerator.ShortcutLocator#getMap()} method to create or 
 * retreive defined shortcut map.
 * 
 * <p>Example
 * {@code
 *   var map : ShortcutMap = ShortcutLocator.getMap( 'map1' );
 *   map.registerEvent( Keyboard.onKeyCONTROL, this, _onKey );
 *   map.registerCommand( Keyboard.onKeyADD, new CommandTest() );
 *   
 *   // Using <strong>AsWing component</strong>
 *   var button1 : JButton = new JButton( 'bouton 1 action' );
 *   button1.addActionListener( Delegate.create( this, _test1, 'param' ) );
 *   
 *   var button2 : JButton = new JButton( 'bouton 2 action' );
 *   button2.addActionListener( _test2, this );
 *   
 *   map.registerCustomType( FvAsWing.AWSHORTCUT, Keyboard.onKeySUBSTRACT, button1 ); *   map.registerCustomType( FvAsWing.AWSHORTCUT, Keyboard.onKeyADD, button2 );
 *   
 *   map.load();
 * }
 * 
 * @see fever.app.accelerator.ShortcutLocator
 * @see fever.app.accelerator.Keyboard * @see fever.app.accelerator.KeyCombo
 * 
 * @author Romain Ecarnot
 */
class fever.app.accelerator.ShortcutMap
{
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private static var _mType : Map = new Map();
	private static var _globalMap : ShortcutMap;
	private static var _commandType : String = 'cmd';
	private static var _eventType : String = 'evt';
	
	private var _mShortcut : Map;	
	private var _name : String;
	private var _isLoaded : Boolean;
	
		
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Returns the main application keys map.
	 */
	public static function getApplicationMap( ) : ShortcutMap
	{
		if( !_globalMap ) 
		{
			_globalMap = new ShortcutMap( 'FeverGlobalKeyMapping' );
			_globalMap.load();
		}
		return _globalMap;
	}
	
	/**
	 * Adds new type to shortcut map.
	 * 
	 * <p>Uses this method to add custom shortcut handler type.
	 */
	public static function addType( type : String, scopeMethod, parsingMethod : Function ) : Void
	{
		if( !_mType.containsKey( type ) )
		{
			_mType.put( type, new Delegate( scopeMethod, parsingMethod ) );
		}
	}
	
	/**
	 * Registers passed-in {@code shortcut} with {@code command} Command.
	 * 
	 * <p>Example
	 * {@code
	 * 	 map.registerCommand( Keyboard.onKeyA, new MyCommand() );
	 * }
	 * 
	 * @param shortcut {@link fever.app.accelerator.KeyDefinition} object
	 * @param command A <strong>Pixlib</strong> compliant Command 
	 * 
	 * @return {@code true} if registration is ok
	 */
	public function registerCommand( shortcut : KeyDefinition, command : Command ) : Boolean
	{
		if( command  && shortcut )
		{
			if( !isRegistred( shortcut ) && !_globalMap.isRegistred( shortcut ) )
			{
				var id : String = _getCommandID( command );
				if( !_isRegistredCommand( command ) && !_globalMap._isRegistredCommand( command ) )
				{
					_addShortcut( id, _commandType, shortcut, command );
					return true;
				}
			}
			else FeverDebug.WARN( 'shorcut ' + shortcut.getName() + ' is already registred' );
		}
		else return false;
	}
	
	/**
	 * Registers passed-in {@code shortcut} with {@code listener} event listener.
	 * 
	 * <p>Valid call are :
	 * {@code
	 * 	 map.registerEvent( Keyboard.onKeyA, this );
	 *   map.registerEvent( Keyboard.onKeyA, this, _onMyKey );	 *   map.registerEvent( Keyboard.onKeyA, this, _onMyKey, param1, param2, ... );	 *   map.registerEvent( Keyboard.onKeyA, Delegate.create( this, _onMyKey ) );	 *   map.registerEvent( Keyboard.onKeyA, Delegate.create( this, _onMyKey, param1, param2, ... ) );
	 * }
	 * 
	 * @param shortcut {@link fever.app.accelerator.KeyDefinition} object
	 * @param listener Event listener
	 * @param ...
	 * 
	 * @return {@code true} if registration is ok
	 */
	public function registerEvent( shortcut : KeyDefinition, listener ) : Boolean
	{
		if( shortcut && arguments.length > 1 )
		{
			if( !isRegistred( shortcut ) && !_globalMap.isRegistred( shortcut ) )
			{
				var f : Function = arguments[2];
				if (f) listener = _getEventProxy.apply( this, arguments.splice(1) );
				var id : String = _getEventID( listener );
				
				if( !_isRegistredEvent( listener ) && !_globalMap._isRegistredEvent( listener ) )
				{
					_addShortcut( id, _eventType, shortcut, listener );
					return true;
				}
			}
			else FeverDebug.WARN( 'shorcut ' + shortcut.getName() + ' is already registred' );
		}
		else return false;
	}
	
	/**
	 * Registers passed-in {@code shortcut} with {@code o} event handler.
	 * 
	 * <p>Use {@link #registerCommand()} or {@link #registerEvent()} for specific 
	 * command or event registration.
	 * 
	 * <p>{@code type} defines a specific {@code o} parsing when shortcut is activated.
	 * 
	 * <p>Example :
	 * {@code
	 *   // Type identifier already exist in FvAsWing class for component <-> shortcut
	 *   map.registerCustomType( FvAsWing.AWSHORTCUT, Keyboard.onKeyA, myButton );
	 *   
	 *   ShortcutMap.addType( 'myOwnType', this, _getOwnType );
	 *   
	 *   function _getOwnType( o, event : KeyBoardEvent ) 
	 *   {
	 *   	// do someting
	 *   }
	 * }
	 * 
	 * @param type Shortcut processing type	 * @param shortcut {@link fever.app.accelerator.KeyDefinition} object
	 * @param o 
	 * 
	 * @return {@code true} if registration is ok
	 */
	public function registerCustomType( type : String, shortcut : KeyDefinition, o : Object ) : Boolean
	{
		if( type && shortcut && o )
		{
			if( !isRegistred( shortcut ) && !_globalMap.isRegistred( shortcut ) )
			{
				var id : String = _getCustomID( o );
				
				if( !_isRegistredObject( o ) && !_globalMap._isRegistredObject( o ) )
				{
					_addShortcut( id, type, shortcut, o );
					return true;
				}
				else FeverDebug.WARN( 'object is already registred' );
			}
			else FeverDebug.WARN( 'shorcut ' + shortcut.getName() + ' is already registred' );
		}
		else FeverDebug.WARN( 'custom shortcut must be associated with valid object' );
		
		return false;	
	}
	
	/**
	 * Returns {@code true} if passed-in {@code shortcut} is registred in map.
	 * 
	 * @param shortcut A {@link fever.app.accelerator.KeyDefinition} object
	 * 
	 * @return {@code true} if shortcut is registred
	 */
	public function isRegistred( shortcut : KeyDefinition ) : Boolean
	{
		return ( _getIDOf( shortcut ) != null );
	}
	
	/**
	 * Unregisters passed-in {@code shortcut} from map.
	 * 
	 * @param shortcut A {@link fever.app.accelerator.KeyDefinition} object
	 */
	public function unregister( shortcut : KeyDefinition ) : Void
	{
		if( shortcut )
		{
			var id : String = _getIDOf( shortcut );
			if( id )
			{
				 _deleteEvent( shortcut );
				 _mShortcut.remove( id );
			}	
		}
	}
	
	/**
	 * Loads current map.
	 * 
	 * <p>All shortcuts are enabled.
	 */
	public function load() : Void
	{
		if( !_isLoaded ) _activate();
	}
	
	/**
	 * Unloads current map.
	 * 
	 * <p>All shortcuts are disabled.
	 */
	public function unload() :Void
	{
		if( _isLoaded ) _dispose();
	}
	
	/**
	 * Clears all shorcuts
	 */
	public function clear() : Void
	{
		_dispose();
		_mShortcut = new Map();	
	}
	
	/**
	 * Returns current shortcut map name.
	 */
	public function getName() : String
	{
		return _name;	
	}
	
	/**
	 * Returns string representation.
	 */
	public function toString() : String
	{		
		return Stringifier.parse( this );
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private function ShortcutMap( mapName : String )
	{
		_mShortcut = new Map();
		_name = mapName;
		
		addType( _commandType, this, _executeCommand );		addType( _eventType, this, _dispatchEvent );
		
		_isLoaded = false;
	}
	
	private function _getCommandID( command : Command ) : String
	{
		return _eventType + HashCodeFactory.getKey( command );	
	}
	
	private function _getEventID( listener ) : String
	{
		return _eventType + HashCodeFactory.getKey( listener );	
	}
	
	private function _getCustomID( o ) : String
	{
		return String( HashCodeFactory.getKey( o ) );	
	}
	
	private function _getIDOf( shortcut : KeyDefinition ) : String
	{
		var keys : Iterator = _mShortcut.getKeysIterator();
		var values : Iterator = _mShortcut.getValuesIterator();
		
		while( values.hasNext() )
		{
			var sc : KeyDefinition = values.next().shortcut;
			var id : String = keys.next();
			
			if( shortcut.getName() == sc.getName() ) return id;	
		}
		return null;	
	}
	
	private function _getEventProxy( oL, f : Function ) : Function
	{
		return Delegate.create.apply( Delegate, [oL, f].concat(arguments.splice(2)) );
	}
	
	private function _isRegistredCommand( command : Command ) : Boolean
	{
		return _mShortcut.containsKey( _getCommandID( command ) );	
	}
	
	private function _isRegistredEvent( listener ) : Boolean
	{
		return _mShortcut.containsKey( _getEventID( listener ) );
	}
	
	private function _isRegistredObject( o ) : Boolean
	{
		return _mShortcut.containsKey( o );
	}
	
	private function _addShortcut( id : String, type : String, shortcut : KeyDefinition, args ) : Void
	{
		_mShortcut.put( id, { type : type, target : args, shortcut : shortcut } );
		
		if( _isLoaded ) _buildEvent( type, shortcut, args );	
	}
	
	private function _activate() : Void
	{
		var it : Iterator = _mShortcut.getValuesIterator();
		while( it.hasNext() )
		{
			var o : Object = it.next();
			
			_buildEvent( o.type, o.shortcut, o.target );
		}
		
		_isLoaded = true;
	}
	
	private function _dispose() : Void
	{
		var it : Iterator = _mShortcut.getValuesIterator();
		while( it.hasNext() ) _deleteEvent( it.next().shortcut );
		
		_isLoaded = false;
	}
	
	public function _clear() : Void
	{
		_dispose();
		_mShortcut = new Map();	
	}
	
	private function _buildEvent( type : String, shortcut : KeyDefinition, listener ) : Void
	{
		if( shortcut )
		{
			KeyboardController.getInstance().addKeyListener( 
					shortcut, 
					Delegate.create( this, _onKeyEvent, type, listener )
			);
		}
	}
	
	private function _deleteEvent( shortcut : KeyDefinition ) : Void
	{
		if( shortcut )
		{
			KeyboardController.getInstance().removeKeyListener( shortcut, this );
		}	
	}
	
	private function _onKeyEvent( event : KeyboardEvent, type : String, listener ) : Void
	{
		if ( _mType.containsKey( type ) )
		{
			var d : Delegate = _mType.get( type );
			d.setArguments( listener, event );
			d.execute();
			
		} else FeverDebug.ERROR( type + ' type is not supported!' );
	}
	
	private function _executeCommand( command : Command, event : KeyboardEvent ) : Void
	{
		command.execute();
	}
	
	private function _dispatchEvent( listener, event : KeyboardEvent ) : Void
	{
		var s : String = typeof( listener );
		if ( s == 'object' || s == 'movieclip' ) 
		{
			if ( listener.handleEvent != undefined )
				listener.handleEvent(event);
			else listener[ String( event.getType() ) ]( event );
		}
		else listener.apply( this, [ event ] );	
	}
}